/*Copyright 2014 Rajesh Putta http://www.codesnippets4all.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
* 
*/

package com.codesnippets4all.jthunder.core.execution;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.codesnippets4all.jthunder.core.IPlugin;
import com.codesnippets4all.jthunder.core.config.handlers.AutomationConfig;
import com.codesnippets4all.jthunder.core.config.handlers.Configuration;
import com.codesnippets4all.jthunder.core.config.handlers.ErrorHandlers;
import com.codesnippets4all.jthunder.core.config.handlers.ExceptionMapper;
import com.codesnippets4all.jthunder.core.config.handlers.ExceptionMapperConfig;
import com.codesnippets4all.jthunder.core.config.handlers.LifeCycleConfig;
import com.codesnippets4all.jthunder.core.config.handlers.PhaseConfig;
import com.codesnippets4all.jthunder.core.config.handlers.PluginConfig;
import com.codesnippets4all.jthunder.core.config.handlers.TaskConfig;
import com.codesnippets4all.jthunder.core.config.handlers.TriggersConfig;
import com.codesnippets4all.jthunder.core.config.types.TriggerType;
import com.codesnippets4all.jthunder.core.execution.status.AutomationStatusManager;
import com.codesnippets4all.jthunder.core.execution.status.IStatusContext;
import com.codesnippets4all.jthunder.core.execution.status.ListenersManager;
import com.codesnippets4all.jthunder.core.execution.status.StatusContext;
import com.codesnippets4all.jthunder.core.framework.hierarchy.IHierarchy;
import com.codesnippets4all.jthunder.core.framework.hierarchy.Node;
import com.codesnippets4all.jthunder.core.framework.hierarchy.PhaseHierarchyManager;
import com.codesnippets4all.jthunder.core.state.GlobalContext;
import com.codesnippets4all.jthunder.core.state.IContext;

/*
 * @author Rajesh Putta
 */
public class HierarchicalTaskExecutionManager implements ITaskExecutionManager {
	private static final Logger LOGGER=Logger.getLogger(HierarchicalTaskExecutionManager.class.getName());

	private AutomationConfig automationConfig=null;
	private ListenersManager listenersManager=null;
	private Map<String, PluginConfig> pluginConfigMap=null;
	private ThreadPoolLifecycle executorService=null;
	private AutomationStatusManager taskStatusManager=null;
	private Map<String, ExceptionMapper> exceptionMapperMap=null; 
	private PhaseHierarchyManager hierarchyManager=null;
	private String lifecycle=null;
	
	public HierarchicalTaskExecutionManager(AutomationConfig automationConfig) {
		this.automationConfig=automationConfig;
		this.pluginConfigMap=this.automationConfig.getPluginsConfig().getConfig();
		this.listenersManager=new ListenersManager(this.automationConfig);
		
		ExceptionMapperConfig exceptionMappers=this.automationConfig.getExceptionMappersConfig();
		
		if(exceptionMappers!=null)
		{
			this.exceptionMapperMap=exceptionMappers.getConfig();
		}
		
		this.executorService=new ExecutorServiceLifecycle();
		this.executorService.init();
	}
	
	public void setStatusManager(AutomationStatusManager taskStatusManager) {
		this.taskStatusManager = taskStatusManager;
	}
	
	public void setHierarchyManager(PhaseHierarchyManager hierarchyManager) {
		this.hierarchyManager = hierarchyManager;
	}
	
	public void destroy() {
		this.executorService.destory();
		
		this.pluginConfigMap=null;
		this.listenersManager=null;
		this.automationConfig=null;
	}
	
	public void execute(String lifecycle)
	{
		this.lifecycle=lifecycle;
		
		LifeCycleConfig lifeCycleConfig=this.automationConfig.getLifeCycleConfig().get(lifecycle);
		
		TriggersConfig triggersConfig=lifeCycleConfig.getTriggersConfig();
		IContext context=new GlobalContext();
		
		Map<TriggerType, List<String>>  triggers=null;
		if(triggersConfig!=null)
		{
			triggers= triggersConfig.getConfig();
			
			List<String> preLifecycleListeners = triggers.get(TriggerType.PRE);

			if(preLifecycleListeners!=null)
			{
				LOGGER.log(Level.INFO, "Before executing pre-lifecycle listeners ..."+lifecycle);		

				this.listenersManager.invokePreLifecycleListeners(lifecycle, preLifecycleListeners, context);
				
				LOGGER.log(Level.INFO, "After executing pre-lifecycle listeners ..."+lifecycle);
			}
			
		}
		
		executePhaseHierarchy(hierarchyManager.getHierarchy(), lifeCycleConfig.getPhaseConfigMap(), context, this.taskStatusManager.getPhaseStatusContext());
		
		if(triggersConfig!=null)
		{
			List<String> postLifecycleListeners = triggers.get(TriggerType.POST);
			
			if(postLifecycleListeners!=null)
			{
				LOGGER.log(Level.INFO, "Before executing post-lifecycle listeners ..."+lifecycle);		
				
				this.listenersManager.invokePostLifecycleListeners(lifecycle, postLifecycleListeners, context);
				
				LOGGER.log(Level.INFO, "After executing post-lifecycle listeners ..."+lifecycle);
			}
			
		}
	}
	
	private void executePhaseHierarchy(IHierarchy<PhaseConfig, Node> iHierarchy, Map<String, PhaseConfig> phasesConfig, IContext context, IStatusContext phaseStatusContext)
	{
		Node node=iHierarchy.getHierarchy();
		
		if(node==null)
			return;
		
		executePhases(node.getChildren(), phasesConfig, context, phaseStatusContext);
	}
	
	private void executePhases(List<Node> nodeList, Map<String, PhaseConfig> phasesConfig, final IContext context, IStatusContext phaseStatusContext) {
	
		if(nodeList==null || nodeList.isEmpty())
			return;
		
		Map<String, Future<IStatus>> futurePhaseMap=new HashMap<String, Future<IStatus>>();
		
		final Map<String, IHierarchy<TaskConfig, Node>> taskHierarchyMap=hierarchyManager.getTaskHierarchyMap();
		
		for(final Node phaseNode:nodeList)
		{
			final String phaseName=phaseNode.getName();
			
			final PhaseConfig phaseConfig=phasesConfig.get(phaseName);
			

			TriggersConfig triggersConfig=phaseConfig.getTriggersConfig();
			
			final Map<TriggerType, List<String>> triggers=(triggersConfig!=null)?triggersConfig.getConfig():null;
			
			final ListenersManager listenerManager=this.listenersManager;
			
			Callable<IStatus> callable=new Callable<IStatus>(){

                public IStatus call() throws Exception {
                	List<String> prePhaseTriggers=null;
                	List<String> postPhaseTriggers=null;
                	
                	if(triggers!=null)
                	{
                		prePhaseTriggers=triggers.get(TriggerType.PRE);
                		
                		if(prePhaseTriggers!=null)
                		{
            				LOGGER.log(Level.INFO, "Before executing pre-phase listeners, LifeCycle..."+lifecycle+"\tPhase..."+phaseName);		
                			
                			listenerManager.invokePrePhaseListeners(phaseName, prePhaseTriggers, context);

                			LOGGER.log(Level.INFO, "After executing pre-phase listeners, LifeCycle..."+lifecycle+"\tPhase..."+phaseName);		
                		}
                	}

                	
            		IStatusContext taskStatusContext=null;
                	
                	IHierarchy<TaskConfig, Node> taskHierarchy=taskHierarchyMap.get(phaseName);

        			taskStatusContext=new StatusContext();
        			taskStatusManager.setContext(phaseName, taskStatusContext);
            		
        			LOGGER.log(Level.INFO, "Before executing task hierarchy. Lifecycle Name..."+lifecycle+"\tPhase Name..."+phaseName);			
        			executeTaskHierarchy(taskHierarchy, phaseConfig.getConfig(), context, taskStatusContext);
        			LOGGER.log(Level.INFO, "After executing task hierarchy. Lifecycle Name..."+lifecycle+"\tPhase Name..."+phaseName);
        			
        			// return phase level status
        			IStatus phaseStatus=new Status(StatusType.SUCCESS, null);
        			
                	phaseStatus.setNode(phaseNode);
            		
                	
                	if(triggers!=null)
                	{
            			postPhaseTriggers=triggers.get(TriggerType.POST);
            			
            			if(postPhaseTriggers!=null)
            			{
            				LOGGER.log(Level.INFO, "Before executing post-phase listeners, LifeCycle..."+lifecycle+"\tPhase..."+phaseName);		

            				listenerManager.invokePostPhaseListeners(phaseName, postPhaseTriggers, context);
            				
            				LOGGER.log(Level.INFO, "After executing post-phase listeners, LifeCycle..."+lifecycle+"\tPhase..."+phaseName);		
            			}
                	}
                	
                	return phaseStatus;
                }
			};
			
			Future<IStatus> phaseStatus=this.executorService.submitTask(callable);
			
			futurePhaseMap.put(phaseName, phaseStatus);
		}

		
		
		Set<String> phases=futurePhaseMap.keySet();
		
		while(true)
		{
			boolean executionStatus=true;
			
			for(String phase: phases)
			{
				Future<IStatus> futureTask=futurePhaseMap.get(phase);
				
				// ensures same phase doesn't get triggered multiple times
				if(phaseStatusContext.getStatus(phase)!=null)
					continue;
				
				executionStatus=executionStatus && futureTask.isDone();
				
				IStatus phaseStatus=null;
				if(futureTask.isDone())
				{
					try {
						phaseStatus=futureTask.get();
					} catch (Exception e) {
						
						e.printStackTrace();
						
						if(phaseStatus==null)
							phaseStatus=new Status(phase, StatusType.EXCEPTION, null, Object.class);
					}

					if(phaseStatus.getType()==null)
						phaseStatus.setType(StatusType.NOT_SET);
					
					phaseStatus.setName(phase);
					phaseStatusContext.addStatus(phase, phaseStatus);
					
					if(phaseStatus.getType()==StatusType.SUCCESS)
					{
						executePhases(phaseStatus.getNode().getChildren(), phasesConfig, context, phaseStatusContext);
					}
				}
			}
			
			if(executionStatus)
				break;
		}
	}

	private void executeTaskHierarchy(IHierarchy<TaskConfig, Node> iHierarchy, Map<String, TaskConfig> tasksConfig, IContext context, IStatusContext taskStatusContext)
	{
		Node node=iHierarchy.getHierarchy();
		
		if(node==null)
			return;
		
		executeTasks(node.getChildren(), tasksConfig, context, taskStatusContext);
	}
	
	private void executeTasks(List<Node> nodeList, Map<String, TaskConfig> tasksConfig, final IContext context, IStatusContext taskStatusContext)
	{
		if(nodeList==null || nodeList.isEmpty())
			return;
		
		Map<String, Future<IStatus>> futureTaskMap=new HashMap<String, Future<IStatus>>();
		
		for(final Node taskNode:nodeList)
		{
			final String taskName=taskNode.getName();
			
			TaskConfig taskConfig=tasksConfig.get(taskName);
			
			String plugin=taskConfig.getPluginName();
			
			PluginConfig pluginConfig=this.pluginConfigMap.get(plugin);
			
			final IPlugin pluginObj=pluginConfig.getPlugin();
			
			Configuration config=taskConfig.getConfiguration();
			
			final Map<String, String> configuration = (config!=null)?config.getConfig():new HashMap<String, String>();
			
			TriggersConfig triggersConfig=taskConfig.getTriggersConfig();
			
			final Map<TriggerType, List<String>> triggers=(triggersConfig!=null)?triggersConfig.getConfig():null;
			
			final ListenersManager listenerManager=this.listenersManager;
			
			Callable<IStatus> callable=new Callable<IStatus>(){

                public IStatus call() throws Exception {
                	List<String> preTaskTriggers=null;
                	List<String> postTaskTriggers=null;
                	
                	if(triggers!=null)
                	{
                		preTaskTriggers=triggers.get(TriggerType.PRE);
                		
                		if(preTaskTriggers!=null)
                		{
            				LOGGER.log(Level.INFO, "Before executing pre-task listeners, LifeCycle..."+lifecycle+"\tTask..."+taskName);		

                			listenerManager.invokePreTaskListeners(taskName, preTaskTriggers, context);

                			LOGGER.log(Level.INFO, "After executing pre-task listeners, LifeCycle..."+lifecycle+"\tTask..."+taskName);		
                		}
                	}
                	
                	IStatus taskStatus=pluginObj.process(configuration, context, taskStatusManager);
                	taskStatus.setNode(taskNode);

                	if(triggers!=null)
                	{
            			postTaskTriggers=triggers.get(TriggerType.POST);
            			
            			if(postTaskTriggers!=null)
            			{
            				LOGGER.log(Level.INFO, "Before executing post-task listeners, LifeCycle..."+lifecycle+"\tTask..."+taskName);		
            				
            				listenerManager.invokePostTaskListeners(taskName, postTaskTriggers, context);
            				
            				LOGGER.log(Level.INFO, "After executing post-task listeners, LifeCycle..."+lifecycle+"\tTask..."+taskName);            				
            			}
                	}
                	
                	return taskStatus;
                }
			};
			
			Future<IStatus> taskStatus=this.executorService.submitTask(callable);
			
			futureTaskMap.put(taskName, taskStatus);
		}

		Set<String> tasks=futureTaskMap.keySet();
		
		while(true)
		{
			boolean executionStatus=true;
			
			for(String task: tasks)
			{
				Future<IStatus> futureTask=futureTaskMap.get(task);
				
				// ensures same task doesn't get triggered multiple times
				if(taskStatusContext.getStatus(task)!=null)
					continue;
				
				executionStatus=executionStatus && futureTask.isDone();
				
				IStatus taskStatus=null;
				if(futureTask.isDone())
				{
					try {
						taskStatus=futureTask.get();
					} catch (Exception e) {
						if(taskStatus==null)
							taskStatus=new Status(task, StatusType.EXCEPTION, null, Object.class);
						
						// execute error handlers
						TaskConfig taskConfig=tasksConfig.get(task);
						
						ErrorHandlers errorHandlers=taskConfig.getErrorHandlers();
						
						if(errorHandlers!=null)
						{
							List<String> handlers=errorHandlers.getConfig();
	
							for(String handler: handlers)
							{
								ExceptionMapper mapper=this.exceptionMapperMap.get(handler);
								
								mapper.getExceptionMapper().handleException(e, context);
							}
						}
						else
							e.printStackTrace();
					}
					
					if(taskStatus.getType()==null)
						taskStatus.setType(StatusType.NOT_SET);
					
					taskStatus.setName(task);
					taskStatusContext.addStatus(task, taskStatus);
					
					
					if(taskStatus.getType()==StatusType.SUCCESS)
					{
						executeTasks(taskStatus.getNode().getChildren(), tasksConfig, context, taskStatusContext);
					}
				}
			}
			
			if(executionStatus)
				break;
		}
		
	}
}
